﻿using System;
using SlimDX.Direct3D11;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;
using Buffer = SlimDX.Direct3D11.Buffer;
using System.Drawing;
using WavefrontLoader;


namespace InsTron
{
    class PlayerViewCommons
    {
        public Device Device { get; private set; }
        public GameState GameState { get; private set; }
        public Effect Effect { get; private set; }
        public EffectTechnique Technique_Bike { get; set; }
        public EffectTechnique Technique_Platform { get; private set; }
        public EffectTechnique Technique_PlatformNoReflection { get; set; }
        public EffectTechnique Technique_Wall { get; set; }
        public EffectTechnique Technique_Sky { get; private set; }
        public EffectTechnique Technique_Cube { get; private set; }
        public EffectTechnique Technique_Triangle { get; private set; }
        public InputLayout InputLayout_PostTexNorm { get; private set; }
        public InputLayout InputLayout_PostNorm { get; private set; }
        public InputLayout InputLayout_Post { get; private set; }
        public Texture2DDescription TargetTextureDescription { get; private set; }
        public Texture2DDescription ReflectionTextureDescription { get; private set; }
        public Texture2DDescription DepthTextureDescription { get; private set; }
        public DepthStencilState DepthState { get; private set; }
        public WFOGeometry BikeModel { get; private set; }
        public SimpleModel PlatformModel { get; private set; }
        public SimpleModel WallModelHorizontal { get; private set; }
        public SimpleModel WallModelVertical { get; private set; }
        public SimpleModel SkySphereModel { get; private set; }
        public SimpleModel CubeModel { get; private set; }
        public Texture2D PlatformTexture { get; private set; }
        public Texture2D SkyTexture { get; private set; }
        public Texture2D BikeTexture { get; private set; }
        public Texture2D CubeTexture { get; private set; }
        public Texture2D MoireTexture { get; private set; }
        public Matrix Perspective { get; private set; }
        public Matrix OuterSkyRotationMatrix { get; private set; }
        public Matrix InnerSkyRotationMatrix { get; private set; }
        public Matrix OuterSkyScalingMatrix { get; private set; }
        public Matrix InnerSkyScalingMatrix { get; private set; }

        private Matrix bikeInitialRotationMatrix;


        public PlayerViewCommons(Device device, GameState gameState)
        {
            Device = device;
            GameState = gameState;
            InitializeAll();
        }


        private void InitializeAll()
        {
            PrepareTextureDescriptions();
            PrepareDepthState();
            LoadEffects();
            ConfigureInputLayouts();
            PrepareModels();
            PrepareTransformationMatrices();
            LoadTextures();
            ConfigureShaderConstants();
            Perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)Settings.CurrentResolution.Width / ((float)Settings.CurrentResolution.Height), 0.01f, 500.0f);
        }


        public void DisposeAll()
        {
            Effect.Dispose();
            BikeModel.DisposeAll();
            PlatformModel.DisposeAll();
            SkySphereModel.DisposeAll();
            CubeModel.DisposeAll();
            WallModelHorizontal.DisposeAll();
            WallModelVertical.DisposeAll();
            PlatformTexture.Dispose();
            SkyTexture.Dispose();
            BikeTexture.Dispose();
            CubeTexture.Dispose();
            MoireTexture.Dispose();
            InputLayout_PostTexNorm.Dispose();
            InputLayout_PostNorm.Dispose();
            InputLayout_Post.Dispose();            
            DepthState.Dispose();
        }


        private void PrepareTextureDescriptions()
        {
            TargetTextureDescription = new Texture2DDescription
            {
                Format = Format.R8G8B8A8_UNorm,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                Height = Settings.WorkingResolution.Height,
                Width = Settings.WorkingResolution.Width,
                ArraySize = 1,
                CpuAccessFlags = CpuAccessFlags.None,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None,
                SampleDescription = new SampleDescription(Settings.MsaaSamples, Settings.CoverageSamples),
                Usage = ResourceUsage.Default
            };

            ReflectionTextureDescription = new Texture2DDescription
            {
                Format = Format.R8G8B8A8_UNorm,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                Height = Settings.WorkingResolution.Height,
                Width = Settings.WorkingResolution.Width,
                ArraySize = 1,
                CpuAccessFlags = CpuAccessFlags.None,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None,
                SampleDescription = new SampleDescription(Settings.MsaaSamples, Settings.CoverageSamples),
                Usage = ResourceUsage.Default        
            };

            DepthTextureDescription = new Texture2DDescription
            {
                Format = Format.D24_UNorm_S8_UInt,
                BindFlags = BindFlags.DepthStencil,
                Height = Settings.WorkingResolution.Height,
                Width = Settings.WorkingResolution.Width,
                ArraySize = 1,
                CpuAccessFlags = CpuAccessFlags.None,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None,
                SampleDescription = new SampleDescription(Settings.MsaaSamples, Settings.CoverageSamples),
                Usage = ResourceUsage.Default
            };
        }


        private void PrepareDepthState()
        {
            //DepthStencilStateDescription depthStateDesc = new DepthStencilStateDescription
            //{
            //    IsDepthEnabled = true,
            //    IsStencilEnabled = Settings.QcaaEnabled,
            //    DepthWriteMask = DepthWriteMask.All,
            //    DepthComparison = Comparison.Less,
            //    StencilWriteMask = 0xFF,
            //    StencilReadMask = 0xFF,
            //    FrontFace = new DepthStencilOperationDescription()
            //    {
            //        Comparison = Comparison.Greater,
            //        FailOperation = StencilOperation.Keep,
            //        PassOperation = StencilOperation.Keep,
            //        DepthFailOperation = StencilOperation.Keep
            //    },
            //    BackFace = new DepthStencilOperationDescription()
            //    {
            //        Comparison = Comparison.Greater,
            //        FailOperation = StencilOperation.Keep,
            //        PassOperation = StencilOperation.Keep,
            //        DepthFailOperation = StencilOperation.Keep
            //    },
            //};
            //DepthState = DepthStencilState.FromDescription(Device, depthStateDesc);
            DepthStencilStateDescription depthStateDesc = new DepthStencilStateDescription
            {
                IsDepthEnabled = true,
                IsStencilEnabled = false,
                DepthWriteMask = DepthWriteMask.All,
                DepthComparison = Comparison.Less
            };
            DepthState = DepthStencilState.FromDescription(Device, depthStateDesc);
            
        }

        
        private void LoadEffects()
        {
            string errors;
            using (var bytecode = ShaderBytecode.CompileFromFile("shaders\\PlayerViewEffects.fx", "fx_5_0", 
                ShaderFlags.None, EffectFlags.None, null, new HlslIncludeHandler(), out errors))
            {
                Effect = new Effect(Device, bytecode);
            }
            Technique_Bike = Effect.GetTechniqueByName("Bike");
            Technique_Platform = Effect.GetTechniqueByName("Platform");
            Technique_PlatformNoReflection = Effect.GetTechniqueByName("PlatformNoReflection");
            Technique_Wall = Effect.GetTechniqueByName("Wall");
            Technique_Sky = Effect.GetTechniqueByName("Sky");
            Technique_Cube = Effect.GetTechniqueByName("Cube");
            Technique_Triangle = Effect.GetTechniqueByName("Triangle");
        }


        private void ConfigureShaderConstants()
        {
            using (var resource = new ShaderResourceView(Device, PlatformTexture))
                Effect.GetVariableByName("xPlatformTexture").AsResource().SetResource(resource);
            Effect.GetVariableByName("xPlatformLightColor").AsVector().Set(Constants.PlatformLightColor);
            using (var resource = new ShaderResourceView(Device, BikeTexture))
                Effect.GetVariableByName("xBikeTexture").AsResource().SetResource(resource);
            using (var resource = new ShaderResourceView(Device, SkyTexture))
                Effect.GetVariableByName("xSkyTexture").AsResource().SetResource(resource);
            using (var resource = new ShaderResourceView(Device, CubeTexture))
                Effect.GetVariableByName("xCubeTexture").AsResource().SetResource(resource);
            Effect.GetVariableByName("xSuperSamplingFactor").AsScalar().Set(Settings.SupersamplingFactor);
            Effect.GetVariableByName("xLightKnob").AsScalar().Set(1.0f);
            Effect.GetVariableByName("xWorkingResolution").AsVector().Set(
                new Vector2(Settings.WorkingResolution.Width, Settings.WorkingResolution.Height));
        }


        private void ConfigureInputLayouts()
        {
            ShaderSignature inputSignature;

            inputSignature = Technique_Bike.GetPassByIndex(0).Description.Signature;
            InputLayout_PostTexNorm = new InputLayout(Device, inputSignature, VertexDeclarations.InputElements_PosTexNorm);
            inputSignature.Dispose();

            inputSignature = Technique_Wall.GetPassByIndex(0).Description.Signature;
            InputLayout_PostNorm = new InputLayout(Device, inputSignature, VertexDeclarations.InputElements_PosNorm);
            inputSignature.Dispose();

            inputSignature = Technique_Triangle.GetPassByIndex(0).Description.Signature;
            InputLayout_Post = new InputLayout(Device, inputSignature, VertexDeclarations.InputElements_Pos);
            inputSignature.Dispose();
        }


        private void PrepareModels()
        {
            PlatformModel = SimpleModelsCreator.CreatePlatformModel(Device);
            WallModelHorizontal = SimpleModelsCreator.CreateHorizontalWallModel(Device);
            WallModelVertical = SimpleModelsCreator.CreateVerticalWallModel(Device);
            SkySphereModel = SimpleModelsCreator.CreateSkySphereModel(Device);
            CubeModel = SimpleModelsCreator.CreateCubeFrameModel(Device);
            BikeModel = WFOGeometry.CreateFromStreamFiles(Device, "assets\\bike\\bikeVertexStream.str", "assets\\bike\\bikeIndexStream.str");
        }


        private void PrepareTransformationMatrices()
        {
            bikeInitialRotationMatrix = Matrix.RotationX((float)(-Math.PI / 2.0));
            OuterSkyRotationMatrix = Matrix.Identity;
            InnerSkyRotationMatrix = Matrix.Identity;
            OuterSkyScalingMatrix = Matrix.Scaling(1.1f, 1.1f, 1.1f);
            InnerSkyScalingMatrix = Matrix.Scaling(0.9f, 0.9f, 0.9f);
        }


        private void LoadTextures()
        {
            PlatformTexture = Texture2D.FromFile(Device, "assets\\textures\\platform.bmp");
            BikeTexture = Texture2D.FromFile(Device, "assets\\bike\\MG_MovieCycle_Body_SPPW.bmp");
            SkyTexture = Texture2D.FromFile(Device, "assets\\textures\\sky.jpg");
            CubeTexture = Texture2D.FromFile(Device, "assets\\textures\\cube.png");
            //ImageLoadInformation ili = new ImageLoadInformation
            //{
            //    Width = 256,
            //    Height = 256,
            //    MipLevels = 0,
            //    BindFlags = BindFlags.ShaderResource,
            //    CpuAccessFlags = CpuAccessFlags.None,
            //    Depth = 1,
            //    FilterFlags = FilterFlags.Linear,
            //    FirstMipLevel = 256,
            //    Format = Format.R8G8B8A8_UNorm,
            //    MipFilterFlags = FilterFlags.Box,
            //    OptionFlags = ResourceOptionFlags.None,
            //    Usage = ResourceUsage.Default
            //};

            MoireTexture = Texture2D.FromFile(Device, "assets\\textures\\moire_hi.bmp");
        }


        public Matrix GetBikeRotationMatrix(int idx)
        {
            return bikeInitialRotationMatrix * GameState.PlayerState[idx].GetRotationMatrix();
        }


        public Matrix GetBikeTranslationMatrix(int idx)
        {
            return GameState.PlayerState[idx].GetTranslationMatrix();
        }


        public void RotateSkySphere(long time)
        {
            Vector3 outerAxis = new Vector3(-0.3f, 0.9f, -0.3f);
            Vector3 innerAxis = new Vector3(0.3f, 0.9f, 0.3f);
            OuterSkyRotationMatrix *= Matrix.RotationAxis(outerAxis, (float)time / 25000);
            InnerSkyRotationMatrix *= Matrix.RotationAxis(innerAxis, (float)time / 20000);
        }
    }
}
